package com.syf.kit.config;


import cn.hutool.core.util.StrUtil;
import com.syf.core.engine.SyFrameContext;
import com.syf.core.utils.NewInstanceByClass;
import com.syf.kit.AppPropertiesUtils;
import com.syf.kit.cont.RdsDSCont;
import com.syf.kit.SyfRdsDaoManager;
import com.syf.kit.core.dao.SyfRdsDaoUtilFactory;
import com.syf.kit.core.dao.impl.SyfMultipleRdsDaoUtilFactory;
import com.syf.kit.core.dao.impl.SyfRWRdsDaoUtilFactory;
import com.syf.kit.core.dao.impl.SyfSimpleRdsDaoUtilFactory;
import com.syf.kit.core.dao.mapper.MapperType;
import com.syf.kit.core.ds.RdsDataSourceProvider;
import com.syf.kit.core.ds.config.PooledRdsDbConfig;
import com.syf.kit.core.ds.impl.SyfDataSource;
import com.syf.kit.core.ds.impl.SyfMultipleDataSource;
import com.syf.kit.core.ds.impl.SyfRWDataSource;
import com.syf.kit.core.ds.impl.SyfSingleDataSource;
import com.syf.kit.core.ds.spi.RdsDataSourceFactory;
import com.syf.kit.core.ds.spi.impl.SyfDbcpDataSourceFactory;
import com.syf.kit.enums.DataSourceType;
import com.syf.kit.enums.RDSType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.SpringFactoriesLoader;

import javax.sql.DataSource;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Configuration
@EnableConfigurationProperties(SyRdsDaoConfiguration.class)
public class SyRdsDaoAutoConfig {

    @ConditionalOnMissingBean(SyfDataSource.class)
    @Bean
    SyfDataSource buildSyfDataSource(SyRdsDaoConfiguration configuration) {
        return createRdsDataSource(configuration);
    }


    @ConditionalOnMissingBean(SyfRdsDaoManager.class)
    @Bean("rdsDaoKit")
    SyfRdsDaoManager buildRdsDaoManager(SyRdsDaoConfiguration configuration, SyfDataSource syfDataSource, SyFrameContext syFrameContext) {
        initAppPropertiesUtils(syFrameContext.getProperty("service.actuator.machineId"), syFrameContext.getProperty("service.actuator.idcId"));

        SyfRdsDaoUtilFactory daoUtilFactory;

        if (DataSourceType.SINGLE.equals(syfDataSource.getDsType())) {
            daoUtilFactory = new SyfSimpleRdsDaoUtilFactory(syfDataSource, configuration.getAutoCommit(), MapperType.getInstanceByName(configuration.getMapper_frame_type()));
        } else if (DataSourceType.RW.equals(syfDataSource.getDsType())) {
            daoUtilFactory = new SyfRWRdsDaoUtilFactory(syfDataSource, configuration.getAutoCommit(), MapperType.getInstanceByName(configuration.getMapper_frame_type()));
        } else if (DataSourceType.MULTIPLE.equals(syfDataSource.getDsType())) {
            daoUtilFactory = new SyfMultipleRdsDaoUtilFactory(syfDataSource, configuration.getAutoCommit(), MapperType.getInstanceByName(configuration.getMapper_frame_type()));
        } else {
            throw new RuntimeException("暂不支持此数据源类型:" + syfDataSource.getDsType().getName());
        }

        return new SyfRdsDaoManager(daoUtilFactory);
    }

    private SyfDataSource createRdsDataSource(SyRdsDaoConfiguration configuration) {
        List<PooledRdsDbConfig> jdbcConfigList = configuration.getJdbcConfig();
        if (jdbcConfigList == null || jdbcConfigList.size() == 0) {
            throw new RuntimeException("请配置数据库JDBC连接信息!");
        }

        RDSType rdsType = defineRdsType(configuration.getRdsType());
        DataSourceType dataSourceType = defineDataSourceType(configuration.getDataSourceType());

        RdsDataSourceFactory rdsDataSourceFactory = getDataSourceFactoryBySPI();
        log.info("通过SPI确定数据库连接池是[{}]", rdsDataSourceFactory.getDBPoolName());
        List<PooledRdsDbConfig> dbConfigList = configuration.getJdbcConfig();

        SyfDataSource syfDs;
        if (DataSourceType.SINGLE.equals(dataSourceType)) {
            // 单数据源
            syfDs = new SyfSingleDataSource(rdsType, createPooledDataSource(rdsType, dbConfigList.get(0), rdsDataSourceFactory));
        } else if (DataSourceType.RW.equals(dataSourceType)) {
            // 读-写数据源
            if (dbConfigList.size() < 2) {
                throw new RuntimeException("配置读写数据源，必须配置两个jdbc连接信息!");
            }

            // 写库jdbc信息
            PooledRdsDbConfig rwConfig = null;
            // 读库jdbc信息
            PooledRdsDbConfig readConfig = null;
            for (PooledRdsDbConfig dbConfig : dbConfigList) {
                if (StrUtil.equals(RdsDSCont.RW_DS_NAME, dbConfig.getDsName())) {
                    rwConfig = dbConfig;
                } else if (StrUtil.equals(RdsDSCont.ONLY_READ_DS_NAME, dbConfig.getDsName())) {
                    readConfig = dbConfig;
                }
            }

            if (rwConfig == null || readConfig == null) {
                throw new RuntimeException("读写数据源必须配置名称为[" + RdsDSCont.RW_DS_NAME + "和" + RdsDSCont.ONLY_READ_DS_NAME + "]的jdbc连接信息!");
            }

            syfDs = new SyfRWDataSource(rdsType
                    , createPooledDataSource(rdsType, rwConfig, rdsDataSourceFactory)
                    , createPooledDataSource(rdsType, readConfig, rdsDataSourceFactory)
            );
        } else if (DataSourceType.MULTIPLE.equals(dataSourceType)) {
            Map<String, DataSource> dsMap = new HashMap<>();
            DataSource defaultDs = null;
            for (PooledRdsDbConfig dbConfig : dbConfigList) {
                DataSource ds = createPooledDataSource(rdsType, dbConfig, rdsDataSourceFactory);
                dsMap.put(dbConfig.getDsName(), ds);
                if (StrUtil.equals(RdsDSCont.DEFAULT_DS_NAME, dbConfig.getDsName())) {
                    defaultDs = ds;
                }
            }

            if (defaultDs == null) {
                throw new RuntimeException("Multiple DataSource 必须定义一个名称:" + RdsDSCont.DEFAULT_DS_NAME + "的数据源");
            }

            syfDs = new SyfMultipleDataSource(rdsType, defaultDs, dsMap);
        } else {
            throw new RuntimeException("dataSourceType不在枚举范围内!");
        }

        return new RdsDataSourceProvider(syfDs).getRdsDataSource();
    }

    /**
     * 生成Pooled data source
     */
    private DataSource createPooledDataSource(RDSType rdsType, PooledRdsDbConfig pooledRdsDbConfig, RdsDataSourceFactory rdsDataSourceFactory) {
        if (!pooledRdsDbConfig.getJdbcUrl().contains(rdsType.getJdbcKeyword())) {
            throw new RuntimeException("配置的jdbc-url与关系型数据库不一致!");
        }

        return rdsDataSourceFactory.buildPooledDataSource(rdsType, pooledRdsDbConfig);
    }

    /**
     * 明确关系型数据库类型
     */
    private RDSType defineRdsType(String rdsType) {
        RDSType target = RDSType.getRDSTypeByName(rdsType);
        if (target == null) {
            throw new RuntimeException("暂时不支持此类关系数据库: " + rdsType + "，目前只支持[" + RDSType.getAllNameString() + "]");
        }

        return target;
    }

    /**
     * 明确数据源类型
     */
    private DataSourceType defineDataSourceType(String dsType) {
        DataSourceType target = DataSourceType.getDsTypeByName(dsType);
        if (target == null) {
            throw new RuntimeException("数据源类型配置不正确，目前只支持[" + DataSourceType.getAllNameString() + "] ，但配置值为" + dsType);
        }

        return target;
    }

    private RdsDataSourceFactory getDataSourceFactoryBySPI() {
        // Spring SPI的加载顺序： 应用启动jar的配置 > 依赖包的配置 ; 依赖包的顺序与maven中依赖包的加载顺序一致；
        List<String> factoryNames = SpringFactoriesLoader.loadFactoryNames(RdsDataSourceFactory.class, this.getClass().getClassLoader());
        if (factoryNames.isEmpty()) {
            // 默认为DBCP
            return new SyfDbcpDataSourceFactory();
        }

        String targetName = factoryNames.get(0);
        try {
            return (RdsDataSourceFactory) NewInstanceByClass.buildInstanceOnConstructor(Class.forName(targetName));
        } catch (ClassNotFoundException | InvocationTargetException | InstantiationException | NoSuchMethodException |
                 IllegalAccessException e) {
            log.error("spring.factories中com.syf.core.rds.RdsDataSourceFactory的SPI值:[{}]不正确，它通过动态反射完成对象实例化!", targetName);
            throw new RuntimeException(e);
        }
    }

    /**
     * 设置AppPropertiesUtils对象，确保在初始化RdsDao中Id生成器可用
     */
    private void initAppPropertiesUtils(String machineId, String idcId) {
        AppPropertiesUtils.getInstance(Long.parseLong(machineId), Long.parseLong(idcId));
    }
}
